#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>

int lastCode = 0;
int lastSig = 0;
#define NUM 1024
char lineCommand[NUM];//字符串的大小为1024

//排空，如果为空则++
#define trimSpace(start) do{\
    while(isspace(*start)) ++start;\
}while(0)

#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPPUT_REDIR 3

int redirType = NONE_REDIR;//默认重定义类型
char *redirFile = NULL;//默认重定义的操作文件

//"ls -l -a > myfile.txt " -> "ls -l -a "  "myfile.txt"
void commandCheck(char *commands)
{
    assert(commands);
    char *begin = commands;
    char * end = commands + strlen(commands);
    while( begin < end)
    {
        if(*begin=='>')
        {
            *begin='\0';
            begin++;
            if(*begin=='>')//追加重定向
            {
                begin++;
                trimSpace(begin);
                redirType = APPPUT_REDIR ;
                redirFile = begin;
                break;
            }
            //输出重定向
            trimSpace(begin);
            redirType = OUTPUT_REDIR ;
            redirFile = begin;
            break;

        }
        else if(*begin=='<')//输入重定向
        {
            *begin = '\0';
            begin++;
            trimSpace(begin);
            redirType = INPUT_REDIR;
            redirFile = begin;
            break;
        }
        else 
        {
            begin++;
        }
    }
}


#define OPT_NUM 64  //最多有64个操作
char* myargv[OPT_NUM];//指针数组，存放操作

int main()
{
    while(1)
    {
        //初始化
        redirType = NONE_REDIR;//默认重定义类型
        redirFile = NULL;//默认重定义的操作文件
        errno = 0;

        //输出提示符
        printf("用户名@主机名 当前路径# ");
        fflush(stdout);//刷新缓冲区，确保提示符第一时间打出

        //获取用户输入
        char* s = fgets(lineCommand,sizeof(lineCommand)-1,stdin);//输入的信息储存到lineCommand当中，-1是为'\0'预留一个位置
        assert(s != NULL);//s不能为空
        (void)s;

        //清除'\n'
        lineCommand[strlen(lineCommand)-1]=0;//将尾部的'\n'设为'\0'
        //printf("%s \n",lineCommand);
   
        //"ls -l -a > myfile.txt " -> "ls -l -a "  "myfile.txt"
        //"ls -l -a >> myfile.txt " -> "ls -l -a "  "myfile.txt"
        //"cat < myfile.txt" -> "cat " < "myfile.txt"
        commandCheck(lineCommand);
        //"ls -a -l"---->"ls" "-a" "-l"
        //字符串切割
        myargv[0]=strtok(lineCommand," ");
        //如果没有子串了，strtok返回NULL，而myargv的最后一位也正好必须为NULL
        int i=1;
        //如果是ls命令，添加一个颜色选项"--color=auto"
        if(myargv[0]!=NULL && strcmp(myargv[0],"ls")==0)
        {
            myargv[i++] = (char*)"--color=auto";
        }
        while(myargv[i++]=strtok(NULL," "));//再次切割时，传NULL
        
        //如果是cd命令，不再进入子进程，在父进程完成
        //像这种不需要让我们的子进程来执行，而是让shell自己执行的命令叫——内建/内置命令
        if(myargv[0]!=NULL && strcmp(myargv[0],"cd")==0)
        {
            if(myargv[1]!=NULL) chdir(myargv[1]);
            continue;
        }
        if(myargv[0]!=NULL&&myargv[1]!=NULL&&strcmp(myargv[0],"echo")==0)
        {
            if(strcmp(myargv[1],"$?")==0)
            {
                printf("%d,%d\n",lastSig,lastCode);
            }
            else 
            {
                printf("%s\n",myargv[1]);
            }
            continue;
        }
#ifdef DEBUG//条件编译，if定义了DEBUG宏则执行下列代码，反之...
        for(int i=0;myargv[i];i++)
        {
            printf("myargv[%d]:%s \n",i,myargv[i]);
        }
#endif
        //子进程执行命令
        pid_t id =fork();
        assert(id != -1);
        if(id == 0)
        {
            switch(redirType)
            {
                case NONE_REDIR:
                    //不做处理
                    break;
                case INPUT_REDIR:
                    {
                        int pf = open(redirFile,O_RDONLY);
                        if(pf < 0)
                        {
                            perror("open");
                            exit(errno);
                        }
                        dup2(pf,0);
                        break;
                    }
                case OUTPUT_REDIR:
                case APPPUT_REDIR:  
                    {
                         int flags = O_WRONLY | O_CREAT;
                         if(redirType == APPPUT_REDIR) flags |= O_APPEND;
                         else flags |= O_TRUNC;
                         int fd = open(redirFile,flags,0666);
                         if(fd<0)
                         {
                             perror("open");
                             exit(errno);
                         }
                         dup2(fd,1);
                         break;
                    }
                default:
                    //不做处理
                    break;
            }


            //chdir("/home/newzjj");
            //child
            execvp(myargv[0],myargv);
            exit(1);
        }
        //parent
        int statuc = 0;
        pid_t retid = waitpid(id,&statuc,0);
        assert(retid>0);
        (void)retid;

        lastCode = (statuc>>8)&0xFF;//异常信息
        lastSig = statuc&0x7F;//退出信息
    }    
}
